#!/usr/bin/env python3
# Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
# conditions defined in the file COPYING, which is part of this source code package.


# mypy: disable-error-code="var-annotated"

import time

from cmk.agent_based.legacy.v0_unstable import LegacyCheckDefinition, STATE_MARKERS
from cmk.agent_based.v2 import get_rate, get_value_store, render

check_info = {}

# Example output from agent:
# <<<emcvnx_disks>>>
#
# All Disks Information
# ---------------------
#

# Bus 0 Enclosure 0  Disk 0
# Vendor Id:               SEAGATE
# Product Id:              STE60005 CLAR600
# Product Revision:        ES0F
# Lun:                     Unbound
# Type:                    N/A
# State:                   Unbound
# Hot Spare:               NO
# Prct Rebuilt:            Unbound
# Prct Bound:              Unbound
# Serial Number:           6SL342E6
# Sectors:                 0 (0)
# Capacity:                549691
# Private:                 Unbound
# Bind Signature:          0x0, 0, 0
# Hard Read Errors:        0
# Hard Write Errors:       0
# Soft Read Errors:        0
# Soft Write Errors:       0
# Read Retries:     N/A
# Write Retries:    N/A
# Remapped Sectors:        N/A
# Number of Reads:         15922079
# Number of Writes:        14841793
# Number of Luns:          0
# Raid Group ID:           This disk does not belong to a RAIDGroup
# Clariion Part Number:    DG118032656
# Request Service Time:    N/A
# Read Requests:           15922079
# Write Requests:          14841793
# Kbytes Read:             998099223
# Kbytes Written:          1661571498
# Stripe Boundary Crossing: None
# Drive Type:              SAS
# Clariion TLA Part Number:005049274
# User Capacity:           0
# Idle Ticks:              162808947
# Busy Ticks:              1220056
# Current Speed: 6Gbps
# Maximum Speed: 6Gbps
# Queue Max:        N/A
# Queue Avg:        N/A
# Prct Idle:        0
# Prct Busy:        0
# Hardware Power Savings Qualified: NO
# Hardware Power Savings Eligible: NO
# Power Savings State: Full Power
# Current Power Savings Log Timestamp: N/A
# Spinning Ticks: N/A
# Standby Ticks: N/A
# Number of Spin Ups: N/A
# Arrivals with Nonzero Queue: 8982980
# High Sum of Seeks:       315504963402436
# Idle Ticks SPA:          81201290
# Idle Ticks SPB:          81607657
# Busy Ticks SPA:          812651
# Busy Ticks SPB:          407405
# Queue Length:            83023848
#
# Bus 1 Enclosure 0  Disk 7
# State:                   Removed
#
# Bus 1 Enclosure 0  Disk 8
# Vendor Id:               SEAGATE
# Product Id:              STE60005 CLAR600
# Product Revision:        ES0F
# [...]

# Parse agent output into a dict of the form:
# parsed = {
#    '0/0 Disk 0': {'Hard Read Errors':  '0',
#                   'Hard Write Errors': '0',
#                   'state':              'Unbound'},
#    '1/0 Disk 7': {'state': 'Removed'},
#    '1/0 Disk 8': {'Hard Read Errors': '0',
#                   'Hard Write Errors': '0',
#                   'state': 'Enabled'},
# }


def saveint(i: str) -> int:
    """Tries to cast a string to an integer and return it. In case this
    fails, it returns 0.

    Advice: Please don't use this function in new code. It is understood as
    bad style these days, because in case you get 0 back from this function,
    you can not know whether it is really 0 or something went wrong."""
    try:
        return int(i)
    except (TypeError, ValueError):
        return 0


def parse_emcvnx_disks(string_table):
    parsed = {}
    for line in string_table:
        if len(line) > 4 and line[0] == "Bus" and line[4] == "Disk":
            encid = line[1] + "/" + line[3] + " " + line[4] + " " + line[5]
            enc = {}
            parsed[encid] = enc
        elif len(line) > 1 and line[0] == "State:":
            state = line[-1]
            enc["state"] = state
        elif len(line) > 2 and line[0] == "Hard" and line[2] == "Errors:":
            error_count = saveint(line[-1])
            enc[line[0] + " " + line[1] + " Errors"] = error_count
        elif len(line) > 1 and line[0] == "Kbytes" and line[1] in ["Read:", "Written:"]:
            io_kbytes = saveint(line[-1])
            enc[line[0] + " " + line[1].replace(":", "")] = io_kbytes
    return parsed


def inventory_emcvnx_disks(parsed):
    inventory = []
    for disk in parsed:
        if parsed[disk]["state"] != "Empty":
            inventory.append((disk, {}))
    return inventory


def check_emcvnx_disks(item, params, parsed):
    now = time.time()
    perfdata = []
    if params is None:  # convert legacy params
        params = {}

    data = parsed.get(item)
    if data is None:
        return None

    diskstate = data["state"]
    message = f"Enclosure {item} is {diskstate}"
    if diskstate in ["Unbound", "Hot Spare Ready", "Enabled", "Ready"]:
        nagstate = 0
    elif diskstate == "Rebuilding":
        nagstate = params.get("state_rebuilding", 1)
        message += " %s" % STATE_MARKERS[nagstate]
    else:
        nagstate = 2
        message += " (!!)"
        # on error state all other fields besides "State:" are missing, omitting...
        return nagstate, message

    read_errors = data["Hard Read Errors"]
    message += ", Hard Read Errors: %s" % read_errors
    if read_errors >= params["state_read_error"][1]:
        nagstate = params["state_read_error"][0]
        message += " %s" % STATE_MARKERS[nagstate]

    write_errors = data["Hard Write Errors"]
    message += ", Hard Write Errors: %s" % write_errors
    if write_errors >= params["state_write_error"][1]:
        nagstate = params["state_write_error"][0]
        message += " %s" % STATE_MARKERS[nagstate]

    read_bytes = data["Kbytes Read"] * 1024
    write_bytes = parsed[item]["Kbytes Written"] * 1024
    countername_r = "emcvnx_disks.read_bytes.%s" % item.replace(" ", "_")
    countername_w = "emcvnx_disks.write_bytes.%s" % item.replace(" ", "_")

    read_bytes_per_sec = get_rate(
        get_value_store(), countername_r, now, read_bytes, raise_overflow=True
    )
    message += ", Read: %s" % render.iobandwidth(read_bytes_per_sec)
    perfdata.append(("read", read_bytes_per_sec))

    write_bytes_per_sec = get_rate(
        get_value_store(), countername_w, now, write_bytes, raise_overflow=True
    )
    message += ", Write: %s" % render.iobandwidth(write_bytes_per_sec)
    perfdata.append(("write", write_bytes_per_sec))

    return nagstate, message, perfdata


check_info["emcvnx_disks"] = LegacyCheckDefinition(
    name="emcvnx_disks",
    parse_function=parse_emcvnx_disks,
    service_name="Enclosure %s",
    discovery_function=inventory_emcvnx_disks,
    check_function=check_emcvnx_disks,
    check_ruleset_name="emcvnx_disks",
    check_default_parameters={
        "state_read_error": (2, 2),  # (state, count of errors)
        "state_write_error": (2, 2),  # (state, count of errors)
        "state_rebuilding": 1,
    },
)
